package com.study.util;

import java.security.*;
import javax.crypto.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * @author CSDN 流放深圳
 * @description Java原生的非对称加密工具类
 * @create 2024-04-13 15:55
 * @since 1.0.0
 */
public class NativeSecurityUtil {

    /**
     * 最大加密长度限制
     */
    public static final int MAX_ENCRYPT_LENGTH = 117;

    /**
     * 最大解密长度限制
     */
    public static final int MAX_DECRYPT_LENGTH = 128;


    /**
     * 生成一对公钥&私钥（仅供测试使用，实际应用上只能生成一次，然后把公钥和私钥保存下来。切忌在业务中每次都调用此方法，否则造成秘钥丢失，数据不可解密！！！）
     * @return
     */
    public static Map<String, String> generateKey() {
        Map<String, String> map = new HashMap<>();
        try{
            // 生成密钥对
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(1024); // 设置密钥长度为 1024 位
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            PublicKey publicObj = keyPair.getPublic();
            PrivateKey privateObj = keyPair.getPrivate();
            String publicKey = Base64.getEncoder().encodeToString(publicObj.getEncoded());
            String privateKey = Base64.getEncoder().encodeToString(privateObj.getEncoded());
            map.put("publicKey", publicKey);
            map.put("privateKey", privateKey);
        }catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }


    /****************************** 【方式一：私钥加密 & 公钥解密】  ***********************************************/
    /**
     * 【方式一】私钥加密
     *
     * @param str        待加密字符串
     * @param privateKey 私钥
     * @return
     */
    public static String encryptByPrivateKey(String str, String privateKey) {
        String result = null;
        try{
            byte[] encoded = Base64.getDecoder().decode(privateKey);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateObj = keyFactory.generatePrivate(privateKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, privateObj);

            //加密的字符串长度不能超过 117 个字节，否则报错，需要分段处理
            byte[] resultBytes = getEncryptResult(str, cipher);
            result = Base64.getEncoder().encodeToString(resultBytes);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 【方式一】公钥解密
     *
     * @param str       待解密字符串
     * @param publicKey 公钥
     * @return
     */
    public static String decryptByPublicKey(String str, String publicKey) {
        String result = null;
        try{
            byte[] encoded = Base64.getDecoder().decode(publicKey);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicObj = keyFactory.generatePublic(publicKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, publicObj);
            byte[] decryptedBytes = getDecryptResult(str, cipher);
            result = new String(decryptedBytes, "UTF-8");
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }


    /****************************** 【方式二：公钥加密 & 私钥解密】  ***********************************************/
    /**
     * 【方式二】公钥加密
     * @param str
     * @param publicKey
     * @return
     */
    public static String encryptByPublicKey(String str, String publicKey) {
        String result = null;
        try{
            byte[] encoded = Base64.getDecoder().decode(publicKey);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicObj = keyFactory.generatePublic(publicKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, publicObj);

            //加密的字符串长度不能超过 117 个字节，否则报错，需要分段处理
            byte[] resultBytes = getEncryptResult(str, cipher);
            result = Base64.getEncoder().encodeToString(resultBytes);
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 【方式二】私钥解密
     * @param str
     * @param privateKey
     * @return
     */
    public static String decryptByPrivateKey(String str, String privateKey) {
        String result = null;
        try{
            byte[] encoded = Base64.getDecoder().decode(privateKey);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encoded);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateObj = keyFactory.generatePrivate(privateKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, privateObj);
            byte[] decryptedBytes = getDecryptResult(str, cipher);
            result = new String(decryptedBytes, "UTF-8");
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 加密的字符串长度不能超过 117 个字节，否则报错，需要分段处理
     * @param str
     * @param cipher
     * @return
     */
    private static byte[] getEncryptResult(String str, Cipher cipher) throws Exception{
        byte[] originalBytes = str.getBytes("UTF-8");
        int originalLength = originalBytes.length;
        int offSet = 0;//偏移量
        byte[] resultBytes = {};
        byte[] cache = {};
        while (originalLength - offSet > 0) {
            if (originalLength - offSet > MAX_ENCRYPT_LENGTH) {
                cache = cipher.doFinal(originalBytes, offSet, MAX_ENCRYPT_LENGTH);
                offSet += MAX_ENCRYPT_LENGTH;
            } else {
                cache = cipher.doFinal(originalBytes, offSet, originalLength - offSet);
                offSet = originalLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

    /**
     * 解密的字符串长度不能超过 128 个字节，否则报错，需要分段处理
     * @param str
     * @param cipher
     * @return
     * @throws Exception
     */
    private static byte[] getDecryptResult(String str, Cipher cipher) throws Exception{
        byte[] decryptedBytes = Base64.getDecoder().decode(str.getBytes("UTF-8"));
        int originalLength = decryptedBytes.length;
        int offSet = 0;//偏移量
        byte[] resultBytes = {};
        byte[] cache = {};
        while (originalLength - offSet > 0) {
            if (originalLength - offSet > MAX_DECRYPT_LENGTH) {
                cache = cipher.doFinal(decryptedBytes, offSet, MAX_DECRYPT_LENGTH);
                offSet += MAX_DECRYPT_LENGTH;
            } else {
                cache = cipher.doFinal(decryptedBytes, offSet, originalLength - offSet);
                offSet = originalLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        //首先生成一对公钥&私钥，记得保存下来
        Map<String, String> map = generateKey();
        String privateKey = map.get("privateKey");
        String publicKey = map.get("publicKey");
        System.out.println("私钥 privateKey=" + privateKey);
        System.out.println("公钥 publicKey=" + publicKey);

        String str = "CSDN流放深圳。《观沧海》是东汉末年诗人曹操创作的一首四言诗。这首诗是曹操在碣石山登山望海时，用饱蘸浪漫主义激情的大笔，所勾勒出的大海吞吐日月、包蕴万千的壮丽景象；描绘了祖国河山的雄伟壮丽，既刻画了高山大海的壮阔，更表达了诗人以景托志，胸怀天下的进取精神。全诗语言质朴，想象丰富，气势磅礴，苍凉悲壮。";
        System.out.println("测试字符串=" + str);

        System.out.println("*********************** 【方式一：私钥加密 & 公钥解密】 *************************");
        String encrypt = encryptByPrivateKey(str, privateKey);
        System.out.println("【方式一】私钥加密结果 encrypt=" + encrypt);

        String decrypt = decryptByPublicKey(encrypt, publicKey);
        System.out.println("【方式一】公钥解密结果 decrypt=" + decrypt);

        System.out.println("*********************** 【方式二：公钥加密 & 私钥解密】 *************************");
        String encrypt222 = encryptByPublicKey(str, publicKey);
        System.out.println("【方式二】公钥加密结果 encrypt222=" + encrypt222);

        String decrypt222 = decryptByPrivateKey(encrypt222, privateKey);
        System.out.println("【方式二】私钥解密结果 decrypt222=" + decrypt222);

    }
}
